import serial
import minimalmodbus
import time
import datetime
import struct

class PhotoelectricSwitchTester:
    def __init__(self, port='COM6', baudrate=115200, slave_address=6):
        """
        初始化光电开关测试器
        :param port: 串口端口（与数字量模块一致）
        :param baudrate: 波特率（需与模块配置匹配）
        :param slave_address: 数字量模块从机地址（默认2，可修改）
        """
        # 初始化Modbus RTU通讯
        self.instrument = minimalmodbus.Instrument(port, slave_address)
        self.instrument.serial.baudrate = baudrate
        self.instrument.serial.bytesize = serial.EIGHTBITS
        self.instrument.serial.parity = serial.PARITY_NONE
        self.instrument.serial.stopbits = serial.STOPBITS_ONE
        self.instrument.serial.timeout = 1.5  # 延长超时时间，确保稳定读取
        self.instrument.mode = minimalmodbus.MODE_RTU
        self.instrument.clear_buffers_before_each_transaction = True
        
        # 光电开关对应数字量输入通道（可根据实际接线修改通道号）
        self.switch_channels = {
            "起点开关": 1,    # 对应通道1（测量起点检测）
            "终点开关": 2,    # 对应通道2（测量终点检测）
            "原点开关": 3     # 对应通道3（原点定位检测）
        }
        
        # 数字量输入寄存器地址（参考手册：输入寄存器起始地址0x0000）
        self.input_register_base = 0x0000

    def read_switch_state(self, channel):
        """
        读取单个光电开关状态
        :param channel: 通道号（1-48，对应模块输入通道）
        :return: True-触发（检测到物体），False-未触发，None-读取失败
        """
        try:
            # 计算寄存器地址（通道1对应0x0000，通道n对应0x000(n-1)）
            register_addr = self.input_register_base + (channel - 1)
            
            # 功能码0x04：读取输入寄存器（参考手册2.4节）
            raw_value = self.instrument.read_register(
                registeraddress=register_addr,
                number_of_decimals=0,
                functioncode=4,
                signed=False
            )
            
            # 状态判断：1=触发，0=未触发（参考手册2.4节状态定义）
            return raw_value == 1
        except Exception as e:
            print(f"通道{channel}读取失败：{str(e)}")
            return None

    def read_all_switches(self):
        """读取所有3个光电开关状态并返回字典"""
        results = {}
        for switch_name, channel in self.switch_channels.items():
            state = self.read_switch_state(channel)
            results[switch_name] = {
                "通道": channel,
                "状态": "触发" if state else "未触发" if state is not None else "读取失败",
                "raw_state": state
            }
        return results

    def continuous_test(self, interval=0.5):
        """连续测试模式，实时打印开关状态"""
        print("=" * 60)
        print("光电开关功能验证 - 连续测试模式")
        print(f"串口：{self.instrument.serial.port} | 波特率：{self.instrument.serial.baudrate} | 从机地址：{self.instrument.address}")
        print(f"测试间隔：{interval}秒 | 按Ctrl+C停止测试")
        print("-" * 60)
        print(f"{'时间':<20} {'开关名称':<10} {'通道':<4} {'状态':<10}")
        print("-" * 60)
        
        try:
            while True:
                current_time =  datetime.datetime.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
                all_states = self.read_all_switches()
                
                for switch_name, info in all_states.items():
                    print(f"{current_time:<20} {switch_name:<10} {info['通道']:<4} {info['状态']:<10}")
                
                time.sleep(interval)
                # 每5行输出一次分隔线，便于观察
                if len(all_states) > 0:
                    print("-" * 60)
        
        except KeyboardInterrupt:
            print("\n\n测试被用户中断")
        except Exception as e:
            print(f"\n测试异常：{str(e)}")

    def single_test(self):
        """单次测试模式，读取一次所有开关状态"""
        print("=" * 60)
        print("光电开关功能验证 - 单次测试模式")
        print("-" * 60)
        
        all_states = self.read_all_switches()
        for switch_name, info in all_states.items():
            print(f"{switch_name}（通道{info['通道']}）：{info['状态']}")
        
        print("=" * 60)

if __name__ == "__main__":
    # 配置参数（根据实际硬件修改）
    TEST_PORT = "COM6"          # 数字量模块连接的串口
    TEST_BAUDRATE = 115200      # 模块实际波特率（需与模块配置一致）
    SLAVE_ADDRESS = 6          # 数字量模块从机地址

    # 创建测试实例
    tester = PhotoelectricSwitchTester(
        port=TEST_PORT,
        baudrate=TEST_BAUDRATE,
        slave_address=SLAVE_ADDRESS
    )

    # 选择测试模式（二选一）
    # tester.single_test()  # 单次测试
    tester.continuous_test(interval=0.5)  # 连续测试（推荐）